#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import asyncore
import json
import logging
import socket
import struct
import threading
from asyncore import dispatcher
from threading import Thread

from .props import PUSH_SERV_HOST
from .props import PUSH_SERV_PORT
from .utils import utf8_decode
from .utils import utf8_encode

logging.getLogger().setLevel(logging.INFO)


class NetAdapter(object):

    def __init__(self):
        pass

    def handle_data(self, handler, data):
        pass


class EventType(object):
    INIT = 'init'
    DATA = 'data'


class MsgCtx(object):

    def __init__(self, cmd=EventType.DATA, data=None):
        self.cmd = cmd
        self.data = data

    def to_json(self):
        _dict = dict()
        _dict['cmd'] = self.cmd
        _dict['data'] = self.data
        return json.dumps(_dict)

    @staticmethod
    def ctx_from_json(json_str):
        _dict = json.loads(json_str)
        if _dict is None:
            return None
        return MsgCtx(cmd=_dict.get('cmd'), data=_dict.get('data'))

    def __repr__(self):
        return self.to_json()

    __str__ = __repr__


class CliHandler(dispatcher):

    def __init__(self, host='localhost', port=10110, adapter=NetAdapter()):
        dispatcher.__init__(self)
        self.adapter = adapter
        self.host = host
        self.port = port

    def start(self):
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((self.host, self.port))

    def handle_connect(self):
        logging.info('connect server %s success', self.addr)

    def handle_close(self):
        logging.info('connect close %s', self.addr)
        self.close()

    def handle_read(self):
        raw_msglen = super(CliHandler, self).recv(4)
        if not raw_msglen:
            return None
        msglen = struct.unpack('>I', raw_msglen)[0]
        data = utf8_decode(super(CliHandler, self).recv(msglen))
        self.adapter.handle_data(handler=self, data=data)

    def send(self, data):
        if data is not None:
            bin_msg = utf8_encode(data)
            msg = struct.pack('>I', len(bin_msg)) + bin_msg
            super(CliHandler, self).send(msg)

    def send_cmd(self, cmd=EventType.DATA, data=None):
        json_data = MsgCtx(cmd=cmd, data=data).to_json()
        self.send(data=json_data)


class PushCliAdapter(NetAdapter):
    __instance = None

    def __init__(self):
        pass

    @staticmethod
    def get_instance():
        if PushCliAdapter.__instance is None:
            PushCliAdapter.__instance = PushCliAdapter()
        return PushCliAdapter.__instance

    def handle_data(self, handler, data):
        logging.debug(data)
        ctx = MsgCtx.ctx_from_json(data)
        if ctx.cmd == EventType.INIT:
            self.do_init(handler=handler, data=ctx.data)
        if ctx.cmd == EventType.DATA:
            self.do_print(handler=handler, data=ctx.data)

    def do_init(self, handler, data):
        logging.info('session_id=%s', data)

    def do_print(self, handler, data):
        logging.info(data)


def synchronized(func):
    func.__lock__ = threading.Lock()

    def synced_func(*args, **kws):
        with func.__lock__:
            return func(*args, **kws)

    return synced_func


class NetCli(object):
    netcli = None

    def __init__(self, host='localhost', port=10110, adapter=PushCliAdapter()):
        self.handler = CliHandler(host=host, port=port, adapter=adapter)
        self.t = Thread(target=(lambda: asyncore.loop()), daemon=True)
        self.stated = False
        self.start()

    @staticmethod
    @synchronized
    def get_netcli(host='localhost', port=10110, adapter=PushCliAdapter()):
        if NetCli.netcli is None:
            NetCli.netcli = NetCli(host=host, port=port, adapter=adapter)
        return NetCli.netcli

    def start(self):
        if not self.stated:
            self.handler.start()
            self.t.start()
            self.stated = True


if __name__ == '__main__':
    import time

    cli = NetCli.get_netcli(host=PUSH_SERV_HOST, port=PUSH_SERV_PORT, adapter=PushCliAdapter())
    cli.start()
    names = ['Cathy', 'Julie', 'Tenchael']
    for _name in names:
        cli.handler.send_cmd(cmd=EventType.DATA, data=_name)

    time.sleep(1)
    cli.handler.send_cmd(cmd=EventType.DATA, data='bye')
    time.sleep(1)
    logging.info('end')
